#include <types.h>
#include <arm.h>
#include <stdio.h>
#include <string.h>

/***** Serial I/O code *****/
#define COM1 		0x50000000
#define COM_LCR		0x00			// Out: Line Control Register
#define COM_LCR_DLAB    0x80    // Divisor latch access bit
#define COM_LCR_WLEN8   0x03    // Wordlength: 8 bits
#define COM_FCR		0x08			// Out: FIFO Control Register
#define COM_MCR		0x0C			// Out: Modem Control Register
#define COM_LSR		0x10			// In:  Line Status Register
#define COM_LSR_DATA	0x1			// Data available
#define COM_LSR_TXRDY	0x2			// Transmit buffer avail
#define COM_LSR_TSRE	0x4			// Transmitter off
#define COM_TX 		0x20			// Out: Transmit buffer
#define COM_RX 		0x24			// In:  Receive buffer


#define ULCON0		COM_LCR
#define UFCON0		COM_FCR
#define UMCON0		COM_MCR
#define UTRSTAT0	COM_LSR

/*
static uint16_t *crt_buf;
static uint16_t crt_pos;
static uint16_t addr_6845;
*/

static bool serial_exists = 0;

// TODO
static void
serial_init(void) {
	// Turn off the FIFO
	outb(COM1 + COM_FCR, 0);
	
	// Reception polling/interrupt mode
	outb(COM1 + 0x4, 1);

	// Set speed; requires DLAB latch
	//Line Control Register, set Divisor latch access bit
	//outb(COM1 + COM_LCR, COM_LCR_DLAB);                 //outb(0x03FB, 0x80)
	//Divisor Latch Low (DLAB=1)
	//outb(COM1 + COM_DLL, (uint8_t) (115200 / 9600));    //outb(0x03F8, 12)
	//Divisor Latch Low (DLAB=1)
	//outb(COM1 + COM_DLM, 0);

	// 8 data bits, 1 stop bit, parity off
	outb(COM1 + COM_LCR, COM_LCR_WLEN8);

	// No modem controls
	outb(COM1 + COM_MCR, 0);
	// Enable rcv interrupts
	//outb(COM1 + COM_IER, COM_IER_RDI);

	// Clear any preexisting overrun indications and interrupts
	// Serial port doesn't exist if COM_LSR returns 0xFF
	//serial_exists = (inb(COM1 + COM_LSR) != 0xFF);

	//(void) inb(COM1+COM_IIR);
	//(void) inb(COM1+COM_RX);
	
	serial_exists = 1;
}

static void
serial_putc_sub(int c) {
    outb(COM1 + COM_TX, c);
}

/* serial_putc - print character to serial port */
static void
serial_putc(int c) {
    if (c != '\b') {
        serial_putc_sub(c);
    }
    else {
        serial_putc_sub('\b');
        serial_putc_sub(' ');
        serial_putc_sub('\b');
    }
}

/* *
 * Here we manage the console input buffer, where we stash characters
 * received from the keyboard or serial port whenever the corresponding
 * interrupt occurs.
 * */

#define CONSBUFSIZE 512

static struct {
    uint8_t buf[CONSBUFSIZE];
    uint32_t rpos;
    uint32_t wpos;
} cons;

/* *
 * cons_intr - called by device interrupt routines to feed input
 * characters into the circular console input buffer.
 * */
static void
cons_intr(int (*proc)(void)) {
    int c;
    while ((c = (*proc)()) != -1) {
        if (c != 0) {
            cons.buf[cons.wpos ++] = c;
            if (cons.wpos == CONSBUFSIZE) {
                cons.wpos = 0;
            }
        }
    }
}

/* serial_proc_data - get data from serial port */
static int
serial_proc_data(void) {
    if (!(inb(COM1 + COM_LSR) & COM_LSR_DATA)) {
        return -1;
    }
    int c = inb(COM1 + COM_RX);
    if (c == 127) {
        c = '\b';
    }
    return c;
}

/* serial_intr - try to feed input characters from serial port */
void
serial_intr(void) {
    if (serial_exists) {
        cons_intr(serial_proc_data);
    }
}

// Originally in i386: Keyboard section
// Not implemented

/* cons_init - initializes the console devices */
void
cons_init(void) {
	serial_init();
    if (!serial_exists) {
        cprintf("serial port does not exist!!\n");
    }
}

/* cons_putc - print a single character @c to console devices */
// serial only atm
void
cons_putc(int c) {
	serial_putc(c);
}

/* *
 * cons_getc - return the next input character from console,
 * or 0 if none waiting.
 * */
int
cons_getc(void) {
    int c;

    // poll for any pending input characters,
    // so that this function works even when interrupts are disabled
    // (e.g., when called from the kernel monitor).
    serial_intr();
    //kbd_intr();

    // grab the next character from the input buffer.
    if (cons.rpos != cons.wpos) {
        c = cons.buf[cons.rpos ++];
        if (cons.rpos == CONSBUFSIZE) {
            cons.rpos = 0;
        }
        return c;
    }
    return 0;
}

